//
// Created by zhengjuping on 24-9-9.
//

#include <esp_log.h>
#include <cstring>
#include "include/GattsApplication.h"

#define LOG_TAG "GattsApplication"

#define ADV_CONFIG_FLAG             (1 << 0)
#define SCAN_RSP_CONFIG_FLAG        (1 << 1)

GattsApplication::GattsApplication(uint16_t applicationId,
                                   std::string applicationName):
                                   _applicationId(applicationId),
                                   _gattsIf(-1),
                                   _applicationName(std::move(applicationName)),
                                   _advConfigDone(0){
    _services.clear();
    bzero(&_advParams, sizeof(esp_ble_adv_params_t));
    _advParams.adv_int_min         = 0x20;
    _advParams.adv_int_max         = 0x40;
    _advParams.adv_type            = ADV_TYPE_IND;
    _advParams.own_addr_type       = BLE_ADDR_TYPE_PUBLIC;
    _advParams.channel_map         = ADV_CHNL_ALL;
    _advParams.adv_filter_policy   = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY;
}

void GattsApplication::addService(const std::shared_ptr<GattsService> &service) {
    _services.push_back(service);
}

void GattsApplication::gapEventCallback(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
    switch (event) {
        case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
            handleGapEventAdvRawDataSetComplete();
            break;

        case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT:
            handleGapEventAdvRspRawDataSetComplete();
            break;

        case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
            handleGapEventAdvStartComplete(param);
            break;

        case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
            handleGapupdateConnParamsEvent(param);
            break;

        default:
            break;
    }
}

void GattsApplication::gattsEventCallback(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
                                          esp_ble_gatts_cb_param_t *param) {

    if( event == ESP_GATTS_REG_EVT ){
        if( param->reg.status != ESP_GATT_OK ){
            std::string errorMessage = "error registering the application:" +
                    std::to_string(_applicationId) + ", status:"+std::to_string(param->reg.status);

            throw std::runtime_error(errorMessage);
        }
        _gattsIf = gatts_if;
    }

    switch (event) {
        case ESP_GATTS_REG_EVT:
            handleGattsEventReg( gatts_if );
            break;
        case ESP_GATTS_READ_EVT:
            handleGattsEventRead( gatts_if, param );
            break;
        case ESP_GATTS_WRITE_EVT:
            handleGattsEventWrite( gatts_if, param );
            break;
        case ESP_GATTS_MTU_EVT:
            handleGattsEventMtu( gatts_if, param );
            break;
        case ESP_GATTS_START_EVT:

            break;
        case ESP_GATTS_CONNECT_EVT:
            handleGattsEventConnect( gatts_if, param );
            break;
        case ESP_GATTS_DISCONNECT_EVT:
            handleGattsEventDisconnect( gatts_if, param );
            break;
        case ESP_GATTS_RESPONSE_EVT:
            ESP_LOGD(LOG_TAG, "GATTS event: response completed");
            break;
        case ESP_GATTS_CREAT_ATTR_TAB_EVT:
            handleGattsEventCreateAttrTable( gatts_if, param );
            break;
        default:
            break;
    }

}

uint16_t GattsApplication::getApplicationId() const {
    return _applicationId;
}

void GattsApplication::handleGapEventAdvRawDataSetComplete() {
    _advConfigDone &= (~ADV_CONFIG_FLAG);
    ESP_LOGD(LOG_TAG, "handleGapEventAdvRawDataSetComplete.........:%d", _advConfigDone);
    if (_advConfigDone == 0){
        esp_ble_gap_start_advertising(&_advParams);
    }
}

void GattsApplication::handleGapEventAdvRspRawDataSetComplete() {
    _advConfigDone &= (~SCAN_RSP_CONFIG_FLAG);
    if (_advConfigDone == 0){
        esp_ble_gap_start_advertising(&_advParams);
    }
}

void GattsApplication::handleGapEventAdvStartComplete(esp_ble_gap_cb_param_t *param) {
    if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
        ESP_LOGE(LOG_TAG, "advertising start failed");
    }else{
        ESP_LOGI(LOG_TAG, "advertising start successfully");
    }
}

void GattsApplication::handleGapupdateConnParamsEvent(esp_ble_gap_cb_param_t *param) {
    ESP_LOGI(LOG_TAG, "update connection params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d",
             param->update_conn_params.status,
             param->update_conn_params.min_int,
             param->update_conn_params.max_int,
             param->update_conn_params.conn_int,
             param->update_conn_params.latency,
             param->update_conn_params.timeout);
}

void GattsApplication::handleGattsEventReg(uint16_t gattsIf) {
    if( _advertisementData == nullptr && !_services.empty() ){
        _advertisementData = std::make_unique<AdvertisementData>( _applicationName,
                                                                  _services.front()->getServiceUuid());
    }
    uint8_t rawAdvData[31];
    uint32_t realRawAdvDataLen = 0;
    _advertisementData->toData(rawAdvData, sizeof(rawAdvData), realRawAdvDataLen);

    esp_err_t ret = esp_ble_gap_set_device_name(_applicationName.c_str());
    if( ret != ESP_OK ){
        throw std::runtime_error("error setting device name");
    }

    ret = esp_ble_gap_config_adv_data_raw(rawAdvData, realRawAdvDataLen);
    if( ret != ESP_OK ){
        throw std::runtime_error("error config adv data");
    }

    int32_t srvcInstId = 0;
    for( const auto& service : _services ){
        uint32_t attDbSize = 0;
        const esp_gatts_attr_db_t *attributeTable = service->getAttributeTable(attDbSize);
        for( int32_t i = 0; i < attDbSize; i++ ){
            ESP_LOGE(LOG_TAG, "autoRsp::%d", attributeTable[i].attr_control.auto_rsp);
            esp_log_buffer_hex_internal(LOG_TAG,attributeTable[i].att_desc.uuid_p, attributeTable[i].att_desc.uuid_length, ESP_LOG_WARN);
            ESP_LOGE(LOG_TAG, "perm::%d", attributeTable[i].att_desc.perm);
            ESP_LOGE(LOG_TAG, "max_length::%d", attributeTable[i].att_desc.max_length);
            esp_log_buffer_hex_internal(LOG_TAG,attributeTable[i].att_desc.value, attributeTable[i].att_desc.length, ESP_LOG_WARN);
        }
        esp_ble_gatts_create_attr_tab(attributeTable, gattsIf, attDbSize, srvcInstId);
        service->setSvcId(srvcInstId);
        srvcInstId++;
    }

}

void GattsApplication::handleGattsEventRead(uint16_t gattsIf, esp_ble_gatts_cb_param_t *param) {
    esp_gatt_rsp_t response;
    bzero(&response, sizeof(response));
    response.attr_value.handle = param->read.handle;

    for( const auto& service : _services ){
        if( service->hasHandle(param->read.handle)){
            service->characteristicReadEvent(param->read.handle, response.attr_value.value, response.attr_value.len);
            esp_ble_gatts_send_response(gattsIf, param->read.conn_id, param->read.trans_id, ESP_GATT_OK, &response);
            return;
        }
    }

}

void GattsApplication::handleGattsEventWrite(uint16_t gattsIf, esp_ble_gatts_cb_param_t *param) {
    ESP_LOGD(
            LOG_TAG,
            "WRITE is_prep=%d, need_rsp=%d, handle=%04x",
            param->write.is_prep,
            (int)param->write.need_rsp,
            param->write.handle);

    if (!param->write.is_prep)
    {
        for( const auto& service : _services ){
            if( service->hasHandle(param->write.handle)){
                service->characteristicWriteEvent(param->write.handle, param->write.value, param->write.len);
            }
            if( param->write.need_rsp ){
                esp_ble_gatts_send_response(
                        gattsIf,
                        param->write.conn_id,
                        param->write.trans_id,
                        ESP_GATT_OK,
                        nullptr);
            }
        }
    }

}

void GattsApplication::handleGattsEventMtu(uint16_t gattsIf, esp_ble_gatts_cb_param_t *param) {
    ESP_LOGD(LOG_TAG, "MTU, conn_id=%d, mtu=%d", param->mtu.conn_id, param->mtu.mtu);
}

void GattsApplication::handleGattsEventConnect(uint16_t gattsIf, esp_ble_gatts_cb_param_t *param) {
    ESP_LOGI(
            LOG_TAG,
            "CONNECT, conn_id=%d, address=%02x:%02x:%02x:%02x:%02x:%02x",
            param->connect.conn_id,
            param->connect.remote_bda[0],
            param->connect.remote_bda[1],
            param->connect.remote_bda[2],
            param->connect.remote_bda[3],
            param->connect.remote_bda[4],
            param->connect.remote_bda[5]);

    esp_ble_conn_update_params_t connectionParameters;
    bzero(&connectionParameters, sizeof(connectionParameters));
    memcpy(connectionParameters.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));
    /* For the iOS system, please refer to Apple official documents about the BLE connection parameters restrictions. */
    connectionParameters.latency = 0;
    connectionParameters.min_int = 0x10;    // min_int = 0x10*1.25ms = 20ms
    connectionParameters.max_int = 0x20;    // max_int = 0x20*1.25ms = 40ms
    connectionParameters.timeout = 400;    // timeout = 400*10ms = 4000ms
    for( const auto& service : _services ){
        service->characteristicConnectEvent(gattsIf, param->connect.conn_id);
    }
    if (esp_ble_gap_update_conn_params(&connectionParameters) != ESP_OK)
    {
        ESP_LOGW(LOG_TAG, "Could not update connection parameters! Skipping.");
    }
}

void GattsApplication::handleGattsEventDisconnect(uint16_t gattsIf, esp_ble_gatts_cb_param_t *param) {
    ESP_LOGI(
            LOG_TAG,
            "DISCONNECT, conn_id=%d, address=%02x:%02x:%02x:%02x:%02x:%02x, reason=0x%04x",
            param->disconnect.conn_id,
            param->disconnect.remote_bda[0],
            param->disconnect.remote_bda[1],
            param->disconnect.remote_bda[2],
            param->disconnect.remote_bda[3],
            param->disconnect.remote_bda[4],
            param->disconnect.remote_bda[5],
            param->disconnect.reason);

    for( const auto& service : _services ){
        service->characteristicDisconnectEvent(gattsIf, param->disconnect.conn_id);
    }

    if ( _advConfigDone == 0 )
    {
        esp_ble_gap_start_advertising(&_advParams);
    }
    else
    {
        ESP_LOGW(LOG_TAG, "not starting advertising again. Configuration not yet completed");
    }
}

void GattsApplication::handleGattsEventCreateAttrTable(uint16_t gattsIf, esp_ble_gatts_cb_param_t *param) {
    for( const auto& service : _services ){
        if( service->getSvcId() == param->add_attr_tab.svc_inst_id ){
            service->pushHandles(param->add_attr_tab.handles, param->add_attr_tab.num_handle);

            esp_err_t  ret = esp_ble_gatts_start_service(param->add_attr_tab.handles[0]);
            ESP_LOGI(LOG_TAG, "Service created:%d", ret);
        }
    }
}
